package io.github.huayunliufeng.common.utils;

import com.sun.management.OperatingSystemMXBean;
import io.github.huayunliufeng.common.constant.MemoryUnitEnum;
import io.github.huayunliufeng.common.constant.TimeUnitEnum;
import io.github.huayunliufeng.common.intface.VoidFunction;
import lombok.Setter;

import java.lang.management.ManagementFactory;
import java.util.PriorityQueue;


/**
 * 性能监控器。
 *
 * @author zhongq 2024/5/30 14:28
 * @version 0.0.7
 * @since 0.0.7
 */
public class PerformanceMonitor {
    /**
     * 开始时间, 默认单位是毫秒
     */
    private long startTime;

    private final static Runtime runtime = Runtime.getRuntime();

    /**
     * 初始内存, 默认单位是字节
     */
    private long startMemory;

    private static final OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);

    /**
     * 开始时的cpu时间, 默认单位是纳秒
     */
    private long startCpuTime;

    /**
     * 运行花费的时间, 默认单位是毫秒
     */
    private long spendTime;

    /**
     * 花费的内存, 默认单位是字节
     */
    private long spendMemory;

    /**
     * 花费的cpu时间, 默认单位是纳秒
     */
    private long spendCpuTime;

    /**
     * 如果多次运行, 将累加spendTime, 默认单位是毫秒
     */
    private final PriorityQueue<Long> totalSpendTime;

    /**
     * 如果多次运行, 将累加spendMemory, 默认单位是字节
     */
    private final PriorityQueue<Long> totalSpendMemory;

    /**
     * 如果多次运行, 将累加spendCpuTime, 默认单位是纳秒
     */
    private final PriorityQueue<Long> totalSpendCpuTime;

    @Setter
    private TimeUnitEnum timeUnit;

    @Setter
    private MemoryUnitEnum memoryUnit;

    @Setter
    private TimeUnitEnum cpuTimeUnit;

    private boolean isStart = false;

    private PerformanceMonitor() {
        this(TimeUnitEnum.S, MemoryUnitEnum.MB, TimeUnitEnum.MS);
    }

    private PerformanceMonitor(TimeUnitEnum timeUnit, MemoryUnitEnum memoryUnit, TimeUnitEnum cpuTimeUnit) {
        this.timeUnit = timeUnit;
        this.memoryUnit = memoryUnit;
        this.cpuTimeUnit = cpuTimeUnit;
        this.totalSpendTime = new PriorityQueue<>();
        this.totalSpendMemory = new PriorityQueue<>();
        this.totalSpendCpuTime = new PriorityQueue<>();
    }

    public static PerformanceMonitor getInstance(TimeUnitEnum timeUnit, MemoryUnitEnum memoryUnit, TimeUnitEnum cpuTimeUnit) {
        return new PerformanceMonitor(timeUnit, memoryUnit, cpuTimeUnit);
    }

    public static PerformanceMonitor getInstance() {
        return new PerformanceMonitor();
    }

    public void start() {
        if (isStart) {
            throw new RuntimeException("已经开始过");
        }
        runtime.gc();
        startTime = System.currentTimeMillis();
        startMemory = runtime.totalMemory() - runtime.freeMemory();
        startCpuTime = osBean.getProcessCpuTime();
        isStart = true;
    }

    public void end() {
        if (!isStart) {
            throw new RuntimeException("请先调用start()方法。");
        }
        long endTime = System.currentTimeMillis();
        long endMemory = runtime.totalMemory() - runtime.freeMemory();
        long endCpuTime = osBean.getProcessCpuTime();

        spendTime = endTime - startTime;
        spendMemory = endMemory - startMemory;
        spendCpuTime = endCpuTime - startCpuTime;

        totalSpendTime.offer(spendTime);
        totalSpendMemory.offer(spendMemory);
        totalSpendCpuTime.offer(spendCpuTime);

        isStart = false;
    }

    /**
     * 重置累加值。
     */
    public void resetCount() {
        totalSpendTime.clear();
        totalSpendMemory.clear();
        totalSpendCpuTime.clear();
    }

    public void printOnceDesc() {
        print("本次花费的时间: %f%s, 内存: %f%s, cpu: %f%s\n", spendTime, spendMemory, spendCpuTime);
    }

    public void printAvgDesc() {
        print("花费的平均时间: %f%s, 内存: %f%s, cpu: %f%s\n", calcAvg(totalSpendTime), calcAvg(totalSpendMemory), calcAvg(totalSpendCpuTime));
    }

    private double calcAvg(PriorityQueue<Long> pq) {
        // 去掉最大值和最小值
        double sum = 0, total = 0;
        pq.poll();
        while (pq.size() > 1) {
            sum += pq.poll();
            total++;
        }
        return sum / total;
    }

    private void print(String format, double v1, double v2, double v3) {
        System.out.printf(format,
                TimeUnitEnum.timeConvert(v1, TimeUnitEnum.MS, timeUnit),
                timeUnit,
                MemoryUnitEnum.memoryConvert(v2, MemoryUnitEnum.B, memoryUnit),
                memoryUnit,
                TimeUnitEnum.timeConvert(v3, TimeUnitEnum.NS, cpuTimeUnit),
                cpuTimeUnit
        );
    }

    public void runTest(VoidFunction function, int count) throws Exception {
        for (int i = 0; i < count; i++) {
            start();
            function.accept();
            end();
            printOnceDesc();
        }
        printAvgDesc();
    }
}
